﻿namespace Ulid;

/// <summary>
/// 一种随机数生成器，当在同一毫秒内每次请求一个随机值时，它会将随机部分递增 1 位
/// </summary>
/// <remarks>
///     <para>
///         这个 “包装器” 随机数生成器（RNG）基于一个指定的随机数生成器来生成随机字节
///         （该指定的随机数生成器可以是实现了 <see cref="IUlidRng"/> 的任何随机数生成器。
///         然而，当在同一毫秒内调用 <see cref="GetRandomBytes(DateTimeOffset)"/> 时，将返回上一次生成的值加 1 的结果，
///         而不是新的随机值。这就产生了 “单调递增” 的值。 ）
///     </para>
///     <para>
///         为确保在同一毫秒内有足够多的值，对于给定的某一毫秒，所生成的初始随机值会将指定数量的（最高有效）位设置为 0，
///         以帮助防止溢出。默认设置为 0 的（最高有效）位的数量是 10，但这个值是可以指定的，其范围在 0 到 70 之间，
///         其中 0 表示生成的初始随机值最有可能接近溢出情况，而 70 表示这种情况的可能性最低。
///     </para>
/// </remarks>
/// <remarks>
/// 初始化 <see cref="MonotonicUlidRng"/> 类的一个新实例
/// </remarks>
/// <param name="rng">用于获取随机数的 <see cref="IUlidRng"/></param>
/// <param name="lastValue">要 “继续使用” 的上一个值; 默认为空</param>
/// <exception cref="ArgumentNullException">当 <paramref name="rng"/> 为空时抛出</exception>
public class MonotonicUlidRng(IUlidRng rng, Ulid? lastValue = null) : BaseUlidRng
{
    // 用于为当前毫秒确定初始值所基于的内部随机数生成器
    private readonly IUlidRng _rng = rng ?? throw new ArgumentNullException(nameof(rng));

    // 在生成（数据）时用于调用 lock() 方法的对象
    private readonly object _genlock = new();

    // 包含上次调用 GetRandomBytes 方法时的时间戳
    private long _lastgen = Ulid.ToUnixTimeMilliseconds(lastValue == null ? Ulid._epoch : lastValue.Value.Time);
    // 包含上次生成的值
    private byte[] _lastvalue = lastValue == null ? new byte[RANDLEN] : lastValue.Value.Random;

    /// <summary>
    /// 使用默认的 <see cref="IUlidRng"/> 初始化 <see cref="MonotonicUlidRng"/> 类的一个新实例
    /// </summary>
    public MonotonicUlidRng()
        : this(DEFAULTRNG) { }

    public override byte[] GetRandomBytes(DateTimeOffset dateTime)
    {
        var buffer = new byte[RANDLEN];
        Core(buffer, dateTime);
        return buffer;
    }

    public override void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime)
    {
        if (buffer.Length < RANDLEN)
        {
            Throw(buffer.Length);
            static void Throw(int len) => throw new ArgumentException($"The given buffer must be at least {RANDLEN} bytes long, actual: {len}");
        }

        Core(buffer, dateTime);
    }

    private void Core(Span<byte> buffer, DateTimeOffset dateTime)
    {
        lock (_genlock)
        {
            // 获取给定日期时间的 Unix 时间戳
            var timestamp = Ulid.ToUnixTimeMilliseconds(dateTime);

            if (timestamp <= _lastgen)  // 与上次生成随机值时的时间戳相同或更早
            {
                // 将随机值增加一
                var i = RANDLEN;
                while (--i >= 0 && ++_lastvalue[i] == 0) { }
                // 如果我一直递增到了 -1，那就出现了溢出情况，此时会抛出异常
                if (i < 0)
                {
                    throw new OverflowException();
                }
            }
            else // 时间戳是新的，所以生成一个新的随机值，并存储这个新的时间戳
            {
                _lastvalue = _rng.GetRandomBytes(dateTime);                 // 使用内部随机数生成器（RNG）来获取字节数组
                _lastvalue[0] = (byte)(_lastvalue[0] & 0x7F);               // 屏蔽掉随机部分的第 0 位

                _lastgen = timestamp;   // 存储上一个时间戳
            }
            _lastvalue.AsSpan().CopyTo(buffer);
        }
    }
}
